#include "File.h"
#include "DirectoryException.h"
#include "FileNotFoundException.h"
#include "FreeListException.h"
#include "BlockGroupException.h"
#include <math.h>
#include "Util.h"
#include "MasterBlock.h"
#include "IntArray.h"


using namespace std;

/* TODO: For cprince's testing code, need to have two param constructor. */
File::File(string filename, bool create, bool read_mode, Disk *disk,
  Directory *dir) {
  /* - Find FCB number from Directory using filename.
   * - Verify FCB was found:
   *   - No? does create == true?, if false die
   *   - Yes: Open file with read_mode
   */
  // Setup variables
  m_dir      = dir;
  m_disk     = disk;
  m_filename = filename;
  freelist = new FreeList(disk, false); // don't format the freelist

  // TODO: Check if filename has been opened already.
  open_read  = false;
  open_write = false;

  // Check if file exists 
  bool file_already_exists = m_dir->FindEntry( m_filename.c_str() );
  printf("File.File: File exist in directory? %d\n", file_already_exists);

  // FIXME: !! is for testing. Should be a single !.
  if ( !file_already_exists ) {
    // File not found
    if ( !create ) {
      throw FileNotFoundException();
    }
    else {
      // Create the File
      // + Allocate block(s) from FreeList for FCB.
      // + Add DirectoryEntry for the File's FCB to the Directory.
      // + Create BlockGroup for the File (contents)
      // - Update FCB to where the File (contents) BlockGroup resides
      // - Call open() in write mode.
      printf("File.File: Getting a free block for the new FCB.\n");
      freelist->Output();
      fcb = freelist->UnlinkBlock();

      // No flush() or write() command for FreeList as
      // UnlinkBlock() makes changes to disk.

      if (fcb == NULL)
         throw FreeListException(); // No space left dude!

      // Add pointer to FCB in a DirectoryEntry, allows access to FCB from
      // the named DirectoryEntry (file).
      DirectoryEntry *a_DirEntry = NULL;
      a_DirEntry = m_dir->CreateNewEntry( m_filename.c_str() );
      a_DirEntry->SetFCB( fcb->GetBlockNumber() ); // Set the pointer to the FCB
      printf("File.File: Flushing Directory...\n");
      m_dir->Flush();

      // BlockGroup for File Contents
      file_block_group = freelist->CreateNew();
      if ( file_block_group == NULL ) {
        // Something bad happened when creating the BlockGroup: throw exception
        throw BlockGroupException();
      }

      /* Set FCB Binary data of four four-byte integers, in this order,
       * in big endian (network) byte order:
       * a) Start block number of the file's BlockGroup.
       * b) End block number of the file's BlockGroup.
       * c) Number of blocks in the file's BlockGroup.
       * d) Number of bytes in the file.
       */
      printf("File.File: Setting FCB params...\n");
      fcb->SetInt( file_block_group->GetStartBlockNumber(), START_BLOCK_PTR_INDEX);
      fcb->SetInt( file_block_group->GetEndBlockNumber(),   END_BLOCK_PTR_INDEX  );
      fcb->SetInt( file_block_group->GetNumberOfBlocks(),   NUM_BLOCKS_PTR_INDEX  );
      fcb->SetInt( 0,                                       NUM_BYTES_PTR_INDEX  );

      printf("File.File: Writing FCB to disk...\n");
      if (! fcb->Write(m_disk) )
        throw BlockException();
      /*
       *if (! freelist->Flush() )
       *  throw FreeListException();
      */
      printf("Calling open\n");
      open(OPEN_WRITE_MODE);
      
    }
  }
  else {
    // TODO: Code me after write() works
    // File already exists, open it:
    // Read FCB
    //  given filename, find what FSBlock number the FCB is in.
    //  Read that block number.
    // Read BlockGroup for file.
    // Call open()
    printf("File.File: File already exists attempting to open file: %s\n", filename.c_str());

    DirectoryEntry* directory_entry_for_FCB = m_dir->LookUp( filename.c_str() );

  }

}

File::~File() {
  delete freelist;
}

bool File::close() {
  if (!( open_read || open_write )) {
    printf("File::close: file %s is not open.\n", m_filename.c_str());
    return false;
  }
  else {
    printf("File::close: file %s is open, closing it.\n", m_filename.c_str());
    open_read  = false;
    open_write = false;
    file_pointer = 0;
    // FIXME: This seems right and proper to delete file_block_pointer, however
    // it causes segfaults when re-opening calls Rewind.
    //delete file_block_pointer;
    file_block_pointer = NULL;
    return true;
  }
}

bool File::open(bool mode) {
  // You should not be able to open a file for both reading and writing.
  if ( open_read || open_write ) {
    printf("File::open: file is already open.\n");
    return false; // file is already open, grr!
  }

  printf("File.open: starting to open file mode%d\n", mode);

  // Determination of Open Mode
  if (mode == OPEN_READ_MODE) {
    // Read Mode
    open_read = true;
    file_size = fcb->GetInt( NUM_BYTES_PTR_INDEX );
  }
  else {
    // Write Mode
    open_write = true;
  }

  // TODO: Factor this out if it is different into the above if statements.
  // Probably want to write to the end by default: append.
  // But no fast_forward() in BlockGroup, hm. Might have to write it myself.
  file_block_group->Rewind();
  file_pointer = 0;

  return true;
}

int File::write(unsigned char *data, int data_length) {
  printf("File.write: params are data_length: %d, data: %s\n\n\n\n", data_length, data);
/* Check if file is open in write mode.
 */
  if (! open_write) {
    printf("File.write: ERROR not open in write mode\n");
    return -1;
  }

  int total_bytes_written = 0; // total bytes written for file
  int bytes_written_block = 0;

  printf("File.write: Fast Forwarding\n");
  file_block_group->FastForward();
  file_block_pointer = file_block_group->GetCurrentBlock();
  printf("File.write: file_block_pointer.blocknumber: %d\n", file_block_pointer->GetBlockNumber() );

  unsigned char* current_block_buffer = file_block_pointer->GetBuffer();
  printf("number of freeblocks %d\n", freelist->GetNumberOfBlocks());


  // until we've written as many bytes as the data length, keep writing bytes
  for (int i = 66; total_bytes_written < data_length; bytes_written_block++) {

    // At end of the current block, spawn another from the FreeList.
    if (bytes_written_block > ((file_block_pointer->GetBlockSize() - 1) - sizeof(int) )) {
      //  write FCB, file_block_pointer
      if (! fcb->Write(m_disk) )
        throw BlockException();
      if (! file_block_pointer->Write(m_disk) )
        throw BlockException();

      // Get a new block for more data
      if (freelist->GetNumberOfBlocks() < 1) {
        printf("File::write: not enough space\n");
        throw FreeListException();
        break; // out of loop
      }
      if (! file_block_group->AddBlock() ) {
        throw FreeListException();
      }
      file_block_group->Output();

      printf("File.File: Setting FCB params...\n");
      fcb->SetInt( file_block_group->GetStartBlockNumber(), START_BLOCK_PTR_INDEX);
      fcb->SetInt( file_block_group->GetEndBlockNumber(),   END_BLOCK_PTR_INDEX  );
      fcb->SetInt( file_block_group->GetNumberOfBlocks(),   NUM_BLOCKS_PTR_INDEX  );
      // fcb->SetInt( 0,                                       NUM_BYTES_PTR_INDEX  );

      printf("File.File: Writing FCB to disk...");
      if (! fcb->Write(m_disk) )
        throw BlockException();
      printf("Done with that.\n");
      // Done adding new FSBlock to expand available file storage.
      
      // Load up the new FSBlock
      file_block_pointer = file_block_group->GetCurrentBlock();
      current_block_buffer = file_block_pointer->GetBuffer();
      bytes_written_block = 0; // reset the number of bytes written for the block
    } // End appending another block

    // Concationate the byte onto current_block_buffer
    // The first four bytes of an FSBlock is a pointer to the next block.
    // Don't want to kill that baby.

    current_block_buffer[bytes_written_block + sizeof(int)] = data[ total_bytes_written ];
    //printf("DEBUG: Bytes written for block: %d\n", bytes_written_block);

    total_bytes_written++;
    //bytes_written_block++;
  }
  printf("File.write: should have written %d bytes to file.\n", total_bytes_written);
    // Write FSBlock to disk.
      if (! file_block_pointer->Write(m_disk) )
        throw BlockException();
      printf("File.File: Writing FCB to disk...\n");
      fcb->SetInt( file_size + total_bytes_written,  NUM_BYTES_PTR_INDEX  );
      if (! fcb->Write(m_disk) )
        throw BlockException();
      printf("DEBUG: %d\n", fcb->GetInt(NUM_BYTES_PTR_INDEX));
  file_block_group->Output();
}

int File::read(unsigned char *data, int data_length) {
  printf("File.read: data_length: %d\n", data_length);
  //Check if file is open in read mode.
  if (! open_read) {
    printf("File.read: ERROR not open in read mode\n");
    return -1;
  }

  int total_bytes_read = 0; // total bytes read from file
  int bytes_read_block = 0;

  printf("File.read: Rewinding...\n");
  file_block_group->Rewind(); 
  file_block_pointer = file_block_group->GetCurrentBlock();
  printf("File.read: file_block_pointer.blocknumber: %d\n", file_block_pointer->GetBlockNumber() );

  unsigned char* current_block_buffer = file_block_pointer->GetBuffer();
  current_block_buffer = current_block_buffer + sizeof(int);
  printf("number of freeblocks %d\n", freelist->GetNumberOfBlocks());

  // until we've read as many bytes as the data length, keep writing bytes
  for (int i = 66; total_bytes_read < data_length; bytes_read_block++) {
    if (total_bytes_read >= file_size) {
      printf("File.read: ERROR attempt to read past end of file.\n");
      return total_bytes_read;
      //break;
    }
    if (bytes_read_block > ((file_block_pointer->GetBlockSize() - 1) - sizeof(int) )) {
      
      // Move to the next Block, but first check if there is a next block.
      if ( file_block_pointer->GetBlockNumber() == file_block_group->GetEndBlockNumber() ) {
        printf("File.read: Last block reached\n");
        break;
      }

      file_block_group->GetNextBlock();
      file_block_pointer = file_block_group->GetCurrentBlock();
      current_block_buffer = file_block_pointer->GetBuffer();
      current_block_buffer = current_block_buffer + sizeof(int);
      bytes_read_block = 0;
    }
    memcpy(data, current_block_buffer, 1);
    current_block_buffer++;
    data++;
    total_bytes_read++;
  }
  return total_bytes_read;
}



int File::get_name() {
  return -1;
}

void File::InitClass(Directory *dir, FreeList *mother){
  // FIXME: WTF?
  //Directory *File::m_dir;
  //FreeList *File::m_mother;

}

void File::Test() {
    //char *s = NULL;
    const char *deviceName = "disk1";
    //int numberOfBlocks = 1000;
    int cmd = 0;
    //Disk *disk1 = NULL;
    //int blockNumber = 0;
    //int byteToWrite;
    //unsigned char *block = new unsigned char[Disk::DEFAULT_BLOCK_SIZE];
    FreeList *mother = NULL;
    Directory *dir = NULL;
    char name[256];
    char name2[256];
    DirectoryEntry *entry;
    int fcb;

    Disk *disk = new Disk(deviceName, 
        FreeList::DEFAULT_NUMBER_OF_BLOCKS, Disk::DEFAULT_BLOCK_SIZE, 
        false /* not a physical device */);

    mother = new FreeList(disk, true);
    dir = new Directory(mother, true);

    string a_name = "another-some";

//    File a_file = File(a_name, false, true, disk, dir);
    File a_file = File(a_name, true, true, disk, dir);
    //                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  <--||---->
    unsigned char some_data[] = "abcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyz2lkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyz3lkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyz4lkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfabcdefghijklmnopqrstuvwxyz5lkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyz6lkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfabcdefghijklmnopqrstuvwxAAAAAAAAljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhabcdefghijklmnopqrstuvwxyzlkjsdfljsjflsdkfjdskfsdjlfsdklfjsdljfdslkffjsdhfkjdsfhhhhhh";
    a_file.write(some_data, 3);

    a_file.close();
    a_file.open(OPEN_READ_MODE);

    unsigned char read_back[4096];// = {'\0'};
    int la = a_file.read(read_back, 12);
    printf("Data length %d, %s\n", la, read_back);

    delete dir;
    delete mother;
    
}
